<!DOCTYPE html>
<html style="display: none;" lang="zh">
    <head>
    <meta charset="utf-8">
    <!--
        © Material Theme
        https://github.com/viosey/hexo-theme-material
        Version: 1.5.0 -->
    <script>
        window.materialVersion = "1.5.0"
        // Delete localstorage with these tags
        window.oldVersion = [
            'codestartv1',
            '1.3.4',
            '1.4.0',
            '1.4.0b1'
        ]
    </script>

    <!-- dns prefetch -->
    <meta http-equiv="x-dns-prefetch-control" content="on">



    <link rel="dns-prefetch" href="https://busuanzi.ibruce.info"/>












    <!-- Title -->
    
    <title>
        
            ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成 | 
        
        ElderJames&#39; Blog
    </title>

    <!-- Meta & Info -->
    <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
    <meta name="format-detection" content="telephone=no"/>
    <meta name="theme-color" content="#0097A7">
    <meta name="author" content="James Yeung">
    <meta name="description" itemprop="description" content="学习、札记、分享">
    <meta name="keywords" content="ElderJames&#39; Blog,.NET Core,ASP.NET Core">

    <!-- Import lsloader -->
    <script>(function(){window.lsloader={jsRunSequence:[],jsnamemap:{},cssnamemap:{}};lsloader.removeLS=function(key){try{localStorage.removeItem(key)}catch(e){}};lsloader.setLS=function(key,val){try{localStorage.setItem(key,val)}catch(e){}};lsloader.getLS=function(key){var val="";try{val=localStorage.getItem(key)}catch(e){val=""}return val};versionString="/*"+(window.materialVersion||"unknownVersion")+"*/";lsloader.clean=function(){try{var keys=[];for(var i=0;i<localStorage.length;i++){keys.push(localStorage.key(i))}keys.forEach(function(key){var data=lsloader.getLS(key);if(window.oldVersion){var remove=window.oldVersion.reduce(function(p,c){return p||data.indexOf("/*"+c+"*/")!==-1},false);if(remove){lsloader.removeLS(key)}}})}catch(e){}};lsloader.clean();lsloader.load=function(jsname,jspath,cssonload,isJs){if(typeof cssonload==="boolean"){isJs=cssonload;cssonload=undefined}isJs=isJs||false;cssonload=cssonload||function(){};var code;code=this.getLS(jsname);if(code&&code.indexOf(versionString)===-1){this.removeLS(jsname);this.requestResource(jsname,jspath,cssonload,isJs);return}if(code){var versionNumber=code.split(versionString)[0];if(versionNumber!=jspath){console.log("reload:"+jspath);this.removeLS(jsname);this.requestResource(jsname,jspath,cssonload,isJs);return}code=code.split(versionString)[1];if(isJs){this.jsRunSequence.push({name:jsname,code:code});this.runjs(jspath,jsname,code)}else{document.getElementById(jsname).appendChild(document.createTextNode(code));cssonload()}}else{this.requestResource(jsname,jspath,cssonload,isJs)}};lsloader.requestResource=function(name,path,cssonload,isJs){var that=this;if(isJs){this.iojs(path,name,function(path,name,code){that.setLS(name,path+versionString+code);that.runjs(path,name,code)})}else{this.iocss(path,name,function(code){document.getElementById(name).appendChild(document.createTextNode(code));that.setLS(name,path+versionString+code)},cssonload)}};lsloader.iojs=function(path,jsname,callback){var that=this;that.jsRunSequence.push({name:jsname,code:""});try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(path,jsname,xhr.response);return}}that.jsfallback(path,jsname)}};xhr.send(null)}catch(e){that.jsfallback(path,jsname)}};lsloader.iocss=function(path,jsname,callback,cssonload){var that=this;try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(xhr.response);cssonload();return}}that.cssfallback(path,jsname,cssonload)}};xhr.send(null)}catch(e){that.cssfallback(path,jsname,cssonload)}};lsloader.iofonts=function(path,jsname,callback,cssonload){var that=this;try{var xhr=new XMLHttpRequest;xhr.open("get",path,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){callback(xhr.response);cssonload();return}}that.cssfallback(path,jsname,cssonload)}};xhr.send(null)}catch(e){that.cssfallback(path,jsname,cssonload)}};lsloader.runjs=function(path,name,code){if(!!name&&!!code){for(var k in this.jsRunSequence){if(this.jsRunSequence[k].name==name){this.jsRunSequence[k].code=code}}}if(!!this.jsRunSequence[0]&&!!this.jsRunSequence[0].code&&this.jsRunSequence[0].status!="failed"){var script=document.createElement("script");script.appendChild(document.createTextNode(this.jsRunSequence[0].code));script.type="text/javascript";document.getElementsByTagName("head")[0].appendChild(script);this.jsRunSequence.shift();if(this.jsRunSequence.length>0){this.runjs()}}else if(!!this.jsRunSequence[0]&&this.jsRunSequence[0].status=="failed"){var that=this;var script=document.createElement("script");script.src=this.jsRunSequence[0].path;script.type="text/javascript";this.jsRunSequence[0].status="loading";script.onload=function(){that.jsRunSequence.shift();if(that.jsRunSequence.length>0){that.runjs()}};document.body.appendChild(script)}};lsloader.tagLoad=function(path,name){this.jsRunSequence.push({name:name,code:"",path:path,status:"failed"});this.runjs()};lsloader.jsfallback=function(path,name){if(!!this.jsnamemap[name]){return}else{this.jsnamemap[name]=name}for(var k in this.jsRunSequence){if(this.jsRunSequence[k].name==name){this.jsRunSequence[k].code="";this.jsRunSequence[k].status="failed";this.jsRunSequence[k].path=path}}this.runjs()};lsloader.cssfallback=function(path,name,cssonload){if(!!this.cssnamemap[name]){return}else{this.cssnamemap[name]=1}var link=document.createElement("link");link.type="text/css";link.href=path;link.rel="stylesheet";link.onload=link.onerror=cssonload;var root=document.getElementsByTagName("script")[0];root.parentNode.insertBefore(link,root)};lsloader.runInlineScript=function(scriptId,codeId){var code=document.getElementById(codeId).innerText;this.jsRunSequence.push({name:scriptId,code:code});this.runjs()};lsloader.loadCombo=function(jslist){var updateList="";var requestingModules={};for(var k in jslist){var LS=this.getLS(jslist[k].name);if(!!LS){var version=LS.split(versionString)[0];var code=LS.split(versionString)[1]}else{var version=""}if(version==jslist[k].path){this.jsRunSequence.push({name:jslist[k].name,code:code,path:jslist[k].path})}else{this.jsRunSequence.push({name:jslist[k].name,code:null,path:jslist[k].path,status:"comboloading"});requestingModules[jslist[k].name]=true;updateList+=(updateList==""?"":";")+jslist[k].path}}var that=this;if(!!updateList){var xhr=new XMLHttpRequest;xhr.open("get",combo+updateList,true);xhr.onreadystatechange=function(){if(xhr.readyState==4){if(xhr.status>=200&&xhr.status<300||xhr.status==304){if(xhr.response!=""){that.runCombo(xhr.response,requestingModules);return}}else{for(var i in that.jsRunSequence){if(requestingModules[that.jsRunSequence[i].name]){that.jsRunSequence[i].status="failed"}}that.runjs()}}};xhr.send(null)}this.runjs()};lsloader.runCombo=function(comboCode,requestingModules){comboCode=comboCode.split("/*combojs*/");comboCode.shift();for(var k in this.jsRunSequence){if(!!requestingModules[this.jsRunSequence[k].name]&&!!comboCode[0]){this.jsRunSequence[k].status="comboJS";this.jsRunSequence[k].code=comboCode[0];this.setLS(this.jsRunSequence[k].name,this.jsRunSequence[k].path+versionString+comboCode[0]);comboCode.shift()}}this.runjs()}})();</script>

    <!-- Import queue -->
    <script>function Queue(){this.dataStore=[];this.offer=b;this.poll=d;this.execNext=a;this.debug=false;this.startDebug=c;function b(e){if(this.debug){console.log("Offered a Queued Function.")}if(typeof e==="function"){this.dataStore.push(e)}else{console.log("You must offer a function.")}}function d(){if(this.debug){console.log("Polled a Queued Function.")}return this.dataStore.shift()}function a(){var e=this.poll();if(e!==undefined){if(this.debug){console.log("Run a Queued Function.")}e()}}function c(){this.debug=true}}var queue=new Queue();</script>

    <!-- Favicons -->
    <link rel="icon shortcut" type="image/ico" href="/img/favicon.png">
    <link rel="icon" sizes="192x192" href="/img/favicon.png">
    <link rel="apple-touch-icon" href="/img/favicon.png">

    <!--iOS -->
    <meta name="apple-mobile-web-app-title" content="Title">
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="HandheldFriendly" content="True">
    <meta name="MobileOptimized" content="480">

    <!-- Add to homescreen for Chrome on Android -->
    <meta name="mobile-web-app-capable" content="yes">

    <!-- Add to homescreen for Safari on iOS -->
    <meta name="apple-mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-status-bar-style" content="black">
    <meta name="apple-mobile-web-app-title" content="ElderJames&#39; Blog">

    <!-- Site Verification -->
    
    

    <!-- RSS -->
    
        
            <link rel=alternate type="application/rss+xml" href="rss.xml">
        
    

    <!--[if lte IE 9]>
        <link rel="stylesheet" href="/css/ie-blocker.css">

        
            <script src="/js/ie-blocker.zhCN.js"></script>
        
    <![endif]-->

    <!-- Import CSS -->
    
        <style id="material_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("material_css","/css/material.min.css?976y5g1pRWbrdcUItimr0g==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
        <style id="style_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("style_css","/css/style.min.css?J9YGUOg9VFzvsUAJ8qxFsQ==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>

        
            
                <style id="prettify_css"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("prettify_css","/css/prettify.min.css?UDVrysExI1W3AEkuJF/bUQ==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
                <style id="prettify_theme"></style><script>if(typeof window.lsLoadCSSMaxNums === "undefined")window.lsLoadCSSMaxNums = 0;window.lsLoadCSSMaxNums++;lsloader.load("prettify_theme","/css/prettify/github-v2.min.css?AfzKxt++K+/lhZBlSjnxwg==",function(){if(typeof window.lsLoadCSSNums === "undefined")window.lsLoadCSSNums = 0;window.lsLoadCSSNums++;if(window.lsLoadCSSNums == window.lsLoadCSSMaxNums)document.documentElement.style.display="";}, false)</script>
            
        

    

    

    <!-- Config CSS -->

<!-- Other Styles -->
<style>
  body, html {
    font-family: Roboto, "Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;
    overflow-x: hidden !important;
  }
  
  code {
    font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
  }

  a {
    color: #00838F;
  }

  .mdl-card__media,
  #search-label,
  #search-form-label:after,
  #scheme-Paradox .hot_tags-count,
  #scheme-Paradox .sidebar_archives-count,
  #scheme-Paradox .sidebar-colored .sidebar-header,
  #scheme-Paradox .sidebar-colored .sidebar-badge{
    background-color: #0097A7 !important;
  }

  /* Sidebar User Drop Down Menu Text Color */
  #scheme-Paradox .sidebar-colored .sidebar-nav>.dropdown>.dropdown-menu>li>a:hover,
  #scheme-Paradox .sidebar-colored .sidebar-nav>.dropdown>.dropdown-menu>li>a:focus {
    color: #0097A7 !important;
  }

  #post_entry-right-info,
  .sidebar-colored .sidebar-nav li:hover > a,
  .sidebar-colored .sidebar-nav li:hover > a i,
  .sidebar-colored .sidebar-nav li > a:hover,
  .sidebar-colored .sidebar-nav li > a:hover i,
  .sidebar-colored .sidebar-nav li > a:focus i,
  .sidebar-colored .sidebar-nav > .open > a,
  .sidebar-colored .sidebar-nav > .open > a:hover,
  .sidebar-colored .sidebar-nav > .open > a:focus,
  #ds-reset #ds-ctx .ds-ctx-entry .ds-ctx-head a {
    color: #0097A7 !important;
  }

  .toTop {
    background: #757575 !important;
  }

  .material-layout .material-post>.material-nav,
  .material-layout .material-index>.material-nav,
  .material-nav a {
    color: #757575;
  }

  #scheme-Paradox .MD-burger-layer {
    background-color: #757575;
  }

  #scheme-Paradox #post-toc-trigger-btn {
    color: #757575;
  }

  .post-toc a:hover {
    color: #00838F;
    text-decoration: underline;
  }

</style>


<!-- Theme Background Related-->

    <style>
      body {
        background-color: undefined;
      }

      /* blog_info bottom background */
      #scheme-Paradox .material-layout .something-else .mdl-card__supporting-text {
        background-color: #fff;
      }
    </style>




<!-- Fade Effect -->

    <style>
      .fade {
        transition: all 800ms linear;
        -webkit-transform: translate3d(0,0,0);
        -moz-transform: translate3d(0,0,0);
        -ms-transform: translate3d(0,0,0);
        -o-transform: translate3d(0,0,0);
        transform: translate3d(0,0,0);
        opacity: 1;
      }

      .fade.out{
        opacity: 0;
      }
    </style>


<!-- Import Font -->
<!-- Import Roboto -->

    <style>
        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: 300;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-300.svg#Roboto) format('svg')
        }

        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: regular;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-regular.svg#Roboto) format('svg')
        }

        @font-face {
            font-family: Roboto;
            font-style: normal;
            font-weight: 500;
            src: url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.eot);
            src: local('Roboto'),local('Roboto-Normal'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.eot?#iefix) format('embedded-opentype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.woff2) format('woff2'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.woff) format('woff'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.ttf) format('truetype'),url(https://lib.baomitu.com/fonts/roboto/roboto-v15-latin-500.svg#Roboto) format('svg')
        }
    </style>


<!-- Import Material Icon -->

    <link rel="stylesheet" href="//cdn.bootcss.com/material-design-icons/3.0.1/iconfont/material-icons.min.css">




    <!-- Import jQuery -->
    
        <script>lsloader.load("jq_js","https://cdn.bootcss.com/jquery/2.2.1/jquery.js", true)</script>
    

    <!-- The Open Graph protocol -->
    <meta property="og:url" content="https://yangshunjie.com">
    <meta property="og:type" content="blog">
    <meta property="og:title" content="ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成 | ElderJames&#39; Blog">
    <meta property="og:image" content="https://yangshunjie.com/img/favicon.png" />
    <meta property="og:description" content="学习、札记、分享">
    <meta property="og:article:tag" content=".NET Core"> <meta property="og:article:tag" content="ASP.NET Core"> 

    
        <meta property="article:published_time" content="Wed Nov 29 2017 18:31:34 GMT+0000" />
        <meta property="article:modified_time" content="Fri Dec 15 2017 09:43:03 GMT+0000" />
    

    <!-- The Twitter Card protocol -->
    <meta name="twitter:title" content="ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成 | ElderJames&#39; Blog">
    <meta name="twitter:description" content="学习、札记、分享">
    <meta name="twitter:image" content="https://yangshunjie.com/img/favicon.png">
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:url" content="https://yangshunjie.com" />

    <!-- Add canonical link for SEO -->
    
        <link rel="canonical" href="https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html" />
    

    <!-- Structured-data for SEO -->
    
        


<script type="application/ld+json">
{
    "@context": "https://schema.org",
    "@type": "BlogPosting",
    "mainEntityOfPage": "https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html",
    "headline": "ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成",
    "datePublished": "Wed Nov 29 2017 18:31:34 GMT+0000",
    "dateModified": "Fri Dec 15 2017 09:43:03 GMT+0000",
    "author": {
        "@type": "Person",
        "name": "James Yeung",
        "image": {
            "@type": "ImageObject",
            "url": "https://avatars0.githubusercontent.com/u/7550366?v=4&amp;s=460"
        },
        "description": "学习/创作/分享"
    },
    "publisher": {
        "@type": "Organization",
        "name": "ElderJames&#39; Blog",
        "logo": {
            "@type":"ImageObject",
            "url": "/img/favicon.png"
        }
    },
    "keywords": ",.NET Core,ASP.NET CoreElderJames&#39; Blog",
    "description": "学习、札记、分享",
}
</script>


    

    <!-- Analytics -->
    
    
    

    <!-- Custom Head -->
    

<link rel="stylesheet" href="/css/prism-vs.css" type="text/css">
<link rel="stylesheet" href="/css/prism-line-numbers.css" type="text/css"></head>


    
        <body id="scheme-Paradox" class="lazy">
            <div class="material-layout  mdl-js-layout has-drawer is-upgraded">
                

                <!-- Main Container -->
                <main class="material-layout__content" id="main">

                    <!-- Top Anchor -->
                    <div id="top"></div>

                    
                        <!-- Hamburger Button -->
                        <button class="MD-burger-icon sidebar-toggle">
                            <span class="MD-burger-layer"></span>
                        </button>
                    

                    <!-- Post TOC -->

    
    <!-- Back Button -->
    <!--
    <div class="material-back" id="backhome-div" tabindex="0">
        <a class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon"
           href="#" onclick="window.history.back();return false;"
           target="_self"
           role="button"
           data-upgraded=",MaterialButton,MaterialRipple">
            <i class="material-icons" role="presentation">arrow_back</i>
            <span class="mdl-button__ripple-container">
                <span class="mdl-ripple"></span>
            </span>
        </a>
    </div>
    -->


    <!-- Left aligned menu below button -->
    
    
    <button id="post-toc-trigger-btn"
        class="mdl-button mdl-js-button mdl-button--icon">
        <i class="material-icons">format_list_numbered</i>
    </button>

    <ul class="post-toc-wrap mdl-menu mdl-menu--bottom-left mdl-js-menu mdl-js-ripple-effect" for="post-toc-trigger-btn" style="max-height:80vh; overflow-y:scroll;">
        <ol class="post-toc"><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#前言"><span class="post-toc-number">1.</span> <span class="post-toc-text"><a href="#&#x524D;&#x8A00;" class="headerlink" title="&#x524D;&#x8A00;"></a>&#x524D;&#x8A00;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#背景"><span class="post-toc-number">2.</span> <span class="post-toc-text"><a href="#&#x80CC;&#x666F;" class="headerlink" title="&#x80CC;&#x666F;"></a>&#x80CC;&#x666F;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#原理"><span class="post-toc-number">3.</span> <span class="post-toc-text"><a href="#&#x539F;&#x7406;" class="headerlink" title="&#x539F;&#x7406;"></a>&#x539F;&#x7406;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#实现"><span class="post-toc-number">4.</span> <span class="post-toc-text"><a href="#&#x5B9E;&#x73B0;" class="headerlink" title="&#x5B9E;&#x73B0;"></a>&#x5B9E;&#x73B0;</span></a><ol class="post-toc-child"><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#RouteAsync"><span class="post-toc-number">4.1.</span> <span class="post-toc-text"><a href="#RouteAsync" class="headerlink" title="RouteAsync"></a><code>RouteAsync</code></span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#GetVirtualPath"><span class="post-toc-number">4.2.</span> <span class="post-toc-text"><a href="#GetVirtualPath" class="headerlink" title="GetVirtualPath"></a><code>GetVirtualPath</code></span></a></li><li class="post-toc-item post-toc-level-3"><a class="post-toc-link" href="#IRouter的设置生效"><span class="post-toc-number">4.3.</span> <span class="post-toc-text"><a href="#IRouter&#x7684;&#x8BBE;&#x7F6E;&#x751F;&#x6548;" class="headerlink" title="IRouter&#x7684;&#x8BBE;&#x7F6E;&#x751F;&#x6548;"></a>IRouter&#x7684;&#x8BBE;&#x7F6E;&#x751F;&#x6548;</span></a></li></ol></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#相关小技巧"><span class="post-toc-number">5.</span> <span class="post-toc-text"><a href="#&#x76F8;&#x5173;&#x5C0F;&#x6280;&#x5DE7;" class="headerlink" title="&#x76F8;&#x5173;&#x5C0F;&#x6280;&#x5DE7;"></a>&#x76F8;&#x5173;&#x5C0F;&#x6280;&#x5DE7;</span></a></li><li class="post-toc-item post-toc-level-2"><a class="post-toc-link" href="#总结"><span class="post-toc-number">6.</span> <span class="post-toc-text"><a href="#&#x603B;&#x7ED3;" class="headerlink" title="&#x603B;&#x7ED3;"></a>&#x603B;&#x7ED3;</span></a></li></ol>
    </ul>
    




<!-- Layouts -->

    <!-- Post Module -->
    <div class="material-post_container">

        <div class="material-post mdl-grid">
            <div class="mdl-card mdl-shadow--4dp mdl-cell mdl-cell--12-col">

                <!-- Post Header(Thumbnail & Title) -->
                
    <!-- Paradox Post Header -->
    
        
            <!-- Random Thumbnail -->
            <div class="post_thumbnail-random mdl-card__media mdl-color-text--grey-50">
            <script type="text/ls-javascript" id="post-thumbnail-script">
    var randomNum = Math.floor(Math.random() * 19 + 1);

    $('.post_thumbnail-random').attr('data-original', '/img/random/material-' + randomNum + '.png');
    $('.post_thumbnail-random').addClass('lazy');
</script>

        
    
            <p class="article-headline-p">
                ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成
            </p>
        </div>





                
                    <!-- Paradox Post Info -->
                    <div class="mdl-color-text--grey-700 mdl-card__supporting-text meta">

    <!-- Author Avatar -->
    <div id="author-avatar">
        <img src="https://avatars0.githubusercontent.com/u/7550366?v=4&s=460" width="44px" height="44px" alt="Author Avatar"/>
    </div>
    <!-- Author Name & Date -->
    <div>
        <strong>James Yeung</strong>
        <span>11月 29, 2017</span>
    </div>

    <div class="section-spacer"></div>

    <!-- Favorite -->
    <!--
        <button id="article-functions-like-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon btn-like">
            <i class="material-icons" role="presentation">favorite</i>
            <span class="visuallyhidden">favorites</span>
        </button>
    -->

    <!-- Qrcode -->
    
        <button id="article-functions-qrcode-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
    <i class="material-icons" role="presentation">devices other</i>
    <span class="visuallyhidden">devices other</span>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-functions-qrcode-button">
    <li class="mdl-menu__item">在其它设备中阅读本文章</li>
    
        <img src="">
    
</ul>

    

    <!-- Tags (bookmark) -->
    
    <button id="article-functions-viewtags-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
        <i class="material-icons" role="presentation">bookmark</i>
        <span class="visuallyhidden">bookmark</span>
    </button>
    <ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-functions-viewtags-button">
        <li class="mdl-menu__item">
        <a class="post_tag-link" href="/tags/NET-Core/">.NET Core</a></li><li class="mdl-menu__item"><a class="post_tag-link" href="/tags/ASP-NET-Core/">ASP.NET Core</a>
    </ul>
    

    <!-- Share -->
    <button id="article-fuctions-share-button" class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon">
    <i class="material-icons" role="presentation">share</i>
    <span class="visuallyhidden">share</span>
</button>
<ul class="mdl-menu mdl-menu--bottom-right mdl-js-menu mdl-js-ripple-effect" for="article-fuctions-share-button">
    

    
        
            <!-- Busuanzi Views -->
            <a class="post_share-link" href="#">
                <li class="mdl-menu__item">
                    <span id="busuanzi_container_page_pv">
                        <span id="busuanzi_value_page_pv"></span>&nbsp;浏览量
                    </span>
                </li>
            </a>
        
    

    <!-- Share Weibo -->
    
        <a class="post_share-link" href="http://service.weibo.com/share/share.php?appkey=&title=ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成&url=https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html&pic=https://yangshunjie.com/img/favicon.png&searchPic=false&style=simple" target="_blank">
            <li class="mdl-menu__item">
                分享到微博
            </li>
        </a>
    

    <!-- Share Twitter -->
    

    <!-- Share Facebook -->
    

    <!-- Share Google+ -->
    

    <!-- Share LinkedIn -->
    

    <!-- Share QQ -->
    
        <a class="post_share-link" href="http://connect.qq.com/widget/shareqq/index.html?site=ElderJames&#39; Blog&title=ASP.NET Core 中的SEO优化（3）:自定义路由匹配和生成&summary=学习、札记、分享&pics=https://yangshunjie.com/img/favicon.png&url=https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html" target="_blank">
            <li class="mdl-menu__item">
                分享到 QQ
            </li>
        </a>
    

    <!-- Share Telegram -->
    
</ul>

</div>

                

                <!-- Post Content -->
                <div id="post-content" class="mdl-color-text--grey-700 mdl-card__supporting-text fade out">
    
        <script src="\assets\js\APlayer.min.js"> </script><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>前两篇文章主要总结了CMS系统两个技术点在<strong>ASP.NET Core</strong>中的应用：</p>
<ul>
<li><a href="A-Middleware-Implement-For-Server-Side-Static-Caching-In-AspNetCore.html">《ASP.NET Core 中的SEO优化（1）：中间件实现服务端静态化缓存》</a></li>
<li><a href="A-Middleware-Implement-For-Rendering-Razor-Views-In-AspNetCore.html">《ASP.NET Core 中的SEO优化（2）：中间件中渲染Razor视图》</a></li>
</ul>
<p>而本篇文章，继续介绍另一个技术点：自定义路由匹配和生成。</p>
<h2 id="背景"><a href="#背景" class="headerlink" title="背景"></a>背景</h2><p>在MVC5时代，默认的路由可能就是简单的约定<code>/{controller}/{action}/{id}</code>,第一节对应控制器（Controller）名，第二节对应操作（Action）名,第三节是参数名。<br>在WebApi和ASP.NET Core时代，有了<code>Route</code>特性来指定相应操作的路由指向，可以很灵活地配置RESTful Api。</p>
<p>但是，路由灵活性在注重SEO的CMS系统中有更苛刻的要求。例如：</p>
<ul>
<li>栏目的列表 -&gt;<code>/{父栏目名}/{子栏目名}-{页码}/</code></li>
<li>文章详情页 -&gt;<code>/{栏目名}/{文章名}.html</code></li>
<li>标签页 -&gt;<code>/{标签名}</code></li>
</ul>
<p>这些友好的url链接使用默认的路由约定是很难实现的，当然是可以配置路由规则去传递参数：</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">UseMvc</span><span class="token punctuation">(</span>routes <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
    routes<span class="token punctuation">.</span><span class="token function">MapRoute</span><span class="token punctuation">(</span>
        name<span class="token punctuation">:</span> <span class="token string">"article_list"</span><span class="token punctuation">,</span>
        template<span class="token punctuation">:</span> <span class="token string">"{parentCategory}/{category}-{page}/"</span><span class="token punctuation">,</span>
        defaults<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> controller <span class="token operator">=</span> <span class="token string">"Article"</span><span class="token punctuation">,</span> action <span class="token operator">=</span> <span class="token string">"Index"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    routes<span class="token punctuation">.</span><span class="token function">MapRoute</span><span class="token punctuation">(</span>
        name<span class="token punctuation">:</span> <span class="token string">"article_detail"</span><span class="token punctuation">,</span>
        template<span class="token punctuation">:</span> <span class="token string">"{category}/{article}.html"</span><span class="token punctuation">,</span>
        defaults<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> controller <span class="token operator">=</span> <span class="token string">"Article"</span><span class="token punctuation">,</span> action <span class="token operator">=</span> <span class="token string">"Detail"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    routes<span class="token punctuation">.</span><span class="token function">MapRoute</span><span class="token punctuation">(</span>
        name<span class="token punctuation">:</span> <span class="token string">"tags"</span><span class="token punctuation">,</span>
        template<span class="token punctuation">:</span> <span class="token string">"{tag}/"</span><span class="token punctuation">,</span>
        defaults<span class="token punctuation">:</span> <span class="token keyword">new</span> <span class="token punctuation">{</span> controller <span class="token operator">=</span> <span class="token string">"Article"</span><span class="token punctuation">,</span> action <span class="token operator">=</span> <span class="token string">"Tag"</span> <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>但是，这样配置很繁琐，也不灵活，如果还要传入更多规则，比如不向下匹配，就显得捉襟见肘了。那有没有更灵活的方案呢？当然有，就是本文接下来要介绍的IRouter接口。</p>
<h2 id="原理"><a href="#原理" class="headerlink" title="原理"></a>原理</h2><p>上一节最后介绍的一般路由配置方法，其实是<code>MapRoute</code>方法创建一个个<code>IRouter</code>的实例，添加到<code>IRouteBuilder</code>实例中，具体方法可以<a href="https://github.com/aspnet/Routing/blob/032bcf43b2cefe641fc6ee9ef3ab0769024a182c/src/Microsoft.AspNetCore.Routing/MapRouteRouteBuilderExtensions.cs#L97" target="_blank" rel="noopener">看看源码</a>。</p>
<p>所以我们可以实现一个自定义的<code>IRouter</code>，就能不用默认的约定，去实现我们的苛刻需求。</p>
<p>首先要看看<code>IRouter</code>这个接口的定义，源码在<a href="https://github.com/aspnet/Routing/blob/032bcf43b2cefe641fc6ee9ef3ab0769024a182c/src/Microsoft.AspNetCore.Routing.Abstractions/IRouter.cs" target="_blank" rel="noopener"><code>aspnet/Routing/src/Microsoft.AspNetCore.Routing.Abstractions/IRouter.cs</code></a></p>
<pre class="line-numbers language-csharp"><code class="language-csharp"><span class="token keyword">namespace</span> Microsoft<span class="token punctuation">.</span>AspNetCore<span class="token punctuation">.</span>Routing
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">interface</span> <span class="token class-name">IRouter</span>
    <span class="token punctuation">{</span>
        Task <span class="token function">RouteAsync</span><span class="token punctuation">(</span>RouteContext context<span class="token punctuation">)</span><span class="token punctuation">;</span>

        VirtualPathData <span class="token function">GetVirtualPath</span><span class="token punctuation">(</span>VirtualPathContext context<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h2 id="实现"><a href="#实现" class="headerlink" title="实现"></a>实现</h2><p><code>IRouter</code>接口只有两个方法，本文分别给出代码示例来介绍。</p>
<h3 id="RouteAsync"><a href="#RouteAsync" class="headerlink" title="RouteAsync"></a><code>RouteAsync</code></h3><p>这个方法中实现路由匹配，从路由上下文中获取所需要的数据，存入RouteData字典中，再从控制器取出做相应的处理。</p>
<pre class="line-numbers language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">async</span> Task <span class="token function">RouteAsync</span><span class="token punctuation">(</span>RouteContext context<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">var</span> requestedUrl <span class="token operator">=</span> context<span class="token punctuation">.</span>HttpContext<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>Path<span class="token punctuation">.</span>Value<span class="token punctuation">.</span><span class="token function">TrimStart</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToLower</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> split <span class="token operator">=</span> requestedUrl<span class="token punctuation">.</span><span class="token function">Split</span><span class="token punctuation">(</span><span class="token string">'/'</span><span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>secoend <span class="token operator">!=</span> <span class="token keyword">null</span> <span class="token operator">&amp;&amp;</span> secoend<span class="token punctuation">.</span><span class="token function">EndsWith</span><span class="token punctuation">(</span><span class="token string">".html"</span><span class="token punctuation">)</span> <span class="token operator">&amp;&amp;</span> split<span class="token punctuation">.</span>Length <span class="token operator">==</span> <span class="token number">2</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">var</span> title <span class="token operator">=</span> secoend<span class="token punctuation">.</span><span class="token function">Replace</span><span class="token punctuation">(</span><span class="token string">".html"</span><span class="token punctuation">,</span> <span class="token string">""</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>RouteData<span class="token punctuation">.</span>Values<span class="token punctuation">[</span><span class="token string">"controller"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"Article"</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>RouteData<span class="token punctuation">.</span>Values<span class="token punctuation">[</span><span class="token string">"action"</span><span class="token punctuation">]</span> <span class="token operator">=</span> <span class="token string">"Detail"</span><span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>RouteData<span class="token punctuation">.</span>Values<span class="token punctuation">[</span><span class="token string">"category"</span><span class="token punctuation">]</span> <span class="token operator">=</span> first<span class="token punctuation">;</span>
        context<span class="token punctuation">.</span>RouteData<span class="token punctuation">.</span>Values<span class="token punctuation">[</span><span class="token string">"title"</span><span class="token punctuation">]</span> <span class="token operator">=</span> title<span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
    <span class="token comment" spellcheck="true">//...对请求路径进行一系列的判断</span>

    <span class="token comment" spellcheck="true">//最后注入`MvcRouteHandler`示例执行`RouteAsync`方法，表示匹配成功</span>
    <span class="token keyword">await</span> context<span class="token punctuation">.</span>HttpContext<span class="token punctuation">.</span>RequestServices<span class="token punctuation">.</span><span class="token generic-method function">GetService<span class="token punctuation">&lt;</span>MvcRouteHandler<span class="token punctuation">></span></span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">RouteAsync</span><span class="token punctuation">(</span>context<span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>这样，当请求路由为<code>/news/asp.net.html</code>时，就会匹配到上面的规则，请求进入<code>Article</code>控制器中的<code>Detail</code>操作被处理，并且可以从控制器中的<code>RouteData.Values[&quot;category&quot;]?.ToString();</code>方法拿到所需的数据。</p>
<h3 id="GetVirtualPath"><a href="#GetVirtualPath" class="headerlink" title="GetVirtualPath"></a><code>GetVirtualPath</code></h3><p>这个方法实现路由生成，可以从路由上下文中获取RouteData字典中的数据，进行虚拟路径（区别与真实目录）的生成。</p>
<pre class="line-numbers language-csharp"><code class="language-csharp"><span class="token keyword">public</span> VirtualPathData <span class="token function">GetVirtualPath</span><span class="token punctuation">(</span>VirtualPathContext context<span class="token punctuation">)</span>
<span class="token punctuation">{</span>
    <span class="token keyword">var</span> path <span class="token operator">=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty<span class="token punctuation">;</span>
    <span class="token keyword">var</span> hasController <span class="token operator">=</span> context<span class="token punctuation">.</span>Values<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"controller"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> controller<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> hasAction <span class="token operator">=</span> context<span class="token punctuation">.</span>Values<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"action"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> action<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> hasCategory <span class="token operator">=</span> context<span class="token punctuation">.</span>Values<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"category"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> category<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token keyword">var</span> hasTitle <span class="token operator">=</span> context<span class="token punctuation">.</span>Values<span class="token punctuation">.</span><span class="token function">TryGetValue</span><span class="token punctuation">(</span><span class="token string">"title"</span><span class="token punctuation">,</span> <span class="token keyword">out</span> <span class="token keyword">var</span> title<span class="token punctuation">)</span><span class="token punctuation">;</span>

    <span class="token keyword">if</span> <span class="token punctuation">(</span>hasController <span class="token operator">&amp;&amp;</span> hasAction <span class="token operator">&amp;&amp;</span> hasCategory <span class="token operator">&amp;&amp;</span> hasTitle<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        path <span class="token operator">=</span> $<span class="token string">"/{category/{title}.html"</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">return</span> path <span class="token operator">!=</span> <span class="token keyword">string</span><span class="token punctuation">.</span>Empty <span class="token operator">?</span> <span class="token keyword">new</span> <span class="token class-name">VirtualPathData</span><span class="token punctuation">(</span><span class="token keyword">this</span><span class="token punctuation">,</span> path<span class="token punctuation">)</span> <span class="token punctuation">:</span> <span class="token keyword">null</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>这样，当调用路径生成方法<code>@Url.Action(&quot;Detail&quot;,&quot;Article&quot;,new { title=&quot;asp.net&quot;, category=&quot;news&quot; })</code>,就会生成”/news/asp.net.html”这样的路径来。</p>
<h3 id="IRouter的设置生效"><a href="#IRouter的设置生效" class="headerlink" title="IRouter的设置生效"></a>IRouter的设置生效</h3><p>自定义实现的<code>IRouter</code>如何设置到原有的MVC项目中呢？方法很简单，上面已经简单说道了，其实<code>app.UseMvc</code>这个方法就有添加<code>IRouter</code>的方法:</p>
<pre class="line-numbers language-csharp"><code class="language-csharp">app<span class="token punctuation">.</span><span class="token function">UseMvc</span><span class="token punctuation">(</span>routes <span class="token operator">=</span><span class="token operator">></span>
<span class="token punctuation">{</span>
    <span class="token comment" spellcheck="true">//添加 自定义路由匹配与url生成组件</span>
    routes<span class="token punctuation">.</span>Routes<span class="token punctuation">.</span><span class="token function">Add</span><span class="token punctuation">(</span><span class="token keyword">new</span> <span class="token class-name">RouteProvider</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<p>这里<code>RouteProvider</code>是<code>IRouter</code>的实现，这样自定义的路由提供对象就生效啦！</p>
<h2 id="相关小技巧"><a href="#相关小技巧" class="headerlink" title="相关小技巧"></a>相关小技巧</h2><ol>
<li><p>SEO中会有一条铁规则，就是不是有效链接的情况下只能返回404，比如，手动输入了一个路径，能匹配到文章详情的操作（action），如果数据库中查不出文章，本来应该不能匹配的，但是，如果在匹配的时候查询数据库确认是否存在的话，会加大系统的压力，所以，可以放在操作（action）中查询，查不到再返回<code>NotFound</code>，让上一篇文章<a href="A-Middleware-Implement-For-Rendering-Razor-Views-In-AspNetCore">《ASP.NET Core 中的SEO优化（2）：中间件中渲染Razor视图》</a>中介绍的中间件一并处理输出404页面。</p>
</li>
<li><p>站内链接如果使用带域名的绝对路径，能够提高优化效果，我们可以自己写一个<code>UrlHelper</code>的扩展方法:</p>
</li>
</ol>
<pre class="line-numbers language-csharp"><code class="language-csharp"><span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">class</span> <span class="token class-name">UrlHelperExtensions</span>
<span class="token punctuation">{</span>
    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">string</span> <span class="token function">AbsoluteAction</span><span class="token punctuation">(</span>
        <span class="token keyword">this</span> IUrlHelper helper<span class="token punctuation">,</span>
        <span class="token keyword">string</span> actionName<span class="token punctuation">,</span>
        <span class="token keyword">string</span> controllerName<span class="token punctuation">,</span>
        <span class="token keyword">object</span> routeValues <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">string</span> scheme <span class="token operator">=</span> helper<span class="token punctuation">.</span>ActionContext<span class="token punctuation">.</span>HttpContext<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>Scheme<span class="token punctuation">;</span>
        <span class="token keyword">return</span> helper<span class="token punctuation">.</span><span class="token function">Action</span><span class="token punctuation">(</span>actionName<span class="token punctuation">,</span> controllerName<span class="token punctuation">,</span> routeValues<span class="token punctuation">,</span> scheme<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">string</span> <span class="token function">AbsoluteContent</span><span class="token punctuation">(</span>
        <span class="token keyword">this</span> IUrlHelper helper<span class="token punctuation">,</span>
        <span class="token keyword">string</span> contentPath<span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">return</span> <span class="token keyword">new</span> <span class="token class-name">Uri</span><span class="token punctuation">(</span>helper<span class="token punctuation">.</span>ActionContext<span class="token punctuation">.</span>HttpContext<span class="token punctuation">.</span>Request<span class="token punctuation">.</span><span class="token function">GetUri</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> helper<span class="token punctuation">.</span><span class="token function">Content</span><span class="token punctuation">(</span>contentPath<span class="token punctuation">)</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token function">ToString</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>

    <span class="token keyword">public</span> <span class="token keyword">static</span> <span class="token keyword">string</span> <span class="token function">AbsoluteRouteUrl</span><span class="token punctuation">(</span>
        <span class="token keyword">this</span> IUrlHelper helper<span class="token punctuation">,</span>
        <span class="token keyword">string</span> routeName<span class="token punctuation">,</span>
        <span class="token keyword">object</span> routeValues <span class="token operator">=</span> <span class="token keyword">null</span><span class="token punctuation">)</span>
    <span class="token punctuation">{</span>
        <span class="token keyword">string</span> scheme <span class="token operator">=</span> helper<span class="token punctuation">.</span>ActionContext<span class="token punctuation">.</span>HttpContext<span class="token punctuation">.</span>Request<span class="token punctuation">.</span>Scheme<span class="token punctuation">;</span>
        <span class="token keyword">return</span> helper<span class="token punctuation">.</span><span class="token function">RouteUrl</span><span class="token punctuation">(</span>routeName<span class="token punctuation">,</span> routeValues<span class="token punctuation">,</span> scheme<span class="token punctuation">)</span><span class="token punctuation">;</span>
    <span class="token punctuation">}</span>
<span class="token punctuation">}</span>
<span aria-hidden="true" class="line-numbers-rows"><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span><span></span></span></code></pre>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>本文主要介绍了自定义路由匹配和生成的解决方案，把路由相关的处理集中到一个类中，避免分散在各个视图进行维护。下篇文章，将会介绍自定义视图搜索目录及主题切换。</p>

        
                <blockquote style="margin: 2em 0 0;padding: 0.5em 1em;border-left: 3px solid #F44336;background-color: #F5F5F5;list-style: none;">
                    <p><strong>
                         
                            <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="知识共享许可协议" style="border-width:0" src="https://i.creativecommons.org/l/by-nc-sa/4.0/80x15.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/">知识共享署名-非商业性使用-相同方式共享 4.0 国际许可协议</a>进行许可。
                        </strong>
                        <br>
                        <strong>本文链接：</strong><a href="https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html">https://yangshunjie.com/A-Middleware-Implement-For-Customized-Routing-In-AspNetCore.html</a>
                    </p>
                </blockquote>
        
    

    
</div>


                

                <!-- Post Comments -->
                
                    
    <!-- 使用 Gitalk -->
<div id="gitalk-comment">
    <!-- Gitalk 评论框 -->
<div id="gitalk-container"></div>

<link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">

<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>

<script>
    var gitalk = new Gitalk({
            clientID: '695234dcb3266b06c9bc',
            clientSecret: '9012a4415f4672e4858df564fc9999e9b6d5cec0',
            repo: 'elderjames.github.io',
            owner: 'ElderJames',
            admin: ['ElderJames'],
            // facebook-like distraction free mode
            distractionFreeMode: false
        })
   gitalk.render('gitalk-container')
</script>
</div>
<style>
    #gitalk-comment {
        background-color: #eee;
        padding: 2pc;
    }
</style>

                
            </div>

            <!-- Post Prev & Next Nav -->
            <nav class="material-nav mdl-color-text--grey-50 mdl-cell mdl-cell--12-col">
    <!-- Prev Nav -->
    
        <a href="/Customized-View-Path-And-Theme-Switching-In-AspNetCore.html" id="post_nav-newer" class="prev-content">
            <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-color--white mdl-color-text--grey-900" role="presentation">
                <i class="material-icons">arrow_back</i>
            </button>
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            新篇
        </a>
    

    <!-- Section Spacer -->
    <div class="section-spacer"></div>

    <!-- Next Nav -->
    
        <a href="/A-Middleware-Implement-For-Rendering-Razor-Views-In-AspNetCore.html" id="post_nav-older" class="next-content">
            旧篇
            &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
            <button class="mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon mdl-color--white mdl-color-text--grey-900" role="presentation">
                <i class="material-icons">arrow_forward</i>
            </button>
        </a>
    
</nav>

        </div>
    </div>



                    
                        <!-- Overlay For Active Sidebar -->
<div class="sidebar-overlay"></div>

<!-- Material sidebar -->
<aside id="sidebar" class="sidebar sidebar-colored sidebar-fixed-left" role="navigation">
    <div id="sidebar-main">
        <!-- Sidebar Header -->
        <div class="sidebar-header header-cover" style="background-image: url(/img/sidebar_header.png);">
    <!-- Top bar -->
    <div class="top-bar"></div>

    <!-- Sidebar toggle button -->
    <button type="button" class="sidebar-toggle mdl-button mdl-js-button mdl-js-ripple-effect mdl-button--icon" style="display: initial;" data-upgraded=",MaterialButton,MaterialRipple">
        <i class="material-icons">clear_all</i>
        <span class="mdl-button__ripple-container">
            <span class="mdl-ripple">
            </span>
        </span>
    </button>

    <!-- Sidebar Avatar -->
    <div class="sidebar-image">
        <img src="https://avatars0.githubusercontent.com/u/7550366?v=4&amp;s=460" alt="James Yeung's avatar">
    </div>

    <!-- Sidebar Email -->
    <a data-toggle="dropdown" class="sidebar-brand" href="#settings-dropdown">
        shunjiey@hotmail.com
        <b class="caret"></b>
    </a>
</div>


        <!-- Sidebar Navigation  -->
        <ul class="nav sidebar-nav">
    <!-- User dropdown  -->
    <li class="dropdown">
        <ul id="settings-dropdown" class="dropdown-menu">
            
                <li>
                    <a href="mailto:shunjiey@hotmail.com" target="_blank" title="Email Me">
                        
                            <i class="material-icons sidebar-material-icons sidebar-indent-left1pc-element">email</i>
                        
                        Email Me
                    </a>
                </li>
            
        </ul>
    </li>

    <!-- Homepage -->
    
        <li id="sidebar-first-li">
            <a href="/">
                
                    <i class="material-icons sidebar-material-icons">home</i>
                
                主页
            </a>
        </li>
        
    

    <!-- Archives  -->
    
        <li class="dropdown">
            <a href="#" class="ripple-effect dropdown-toggle" data-toggle="dropdown">
                
                    <i class="material-icons sidebar-material-icons">inbox</i>
                
                    归档
                <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
            <li>
                <a class="sidebar_archives-link" href="/archives/2017/12/">十二月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/11/">十一月 2017<span class="sidebar_archives-count">4</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/09/">九月 2017<span class="sidebar_archives-count">5</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/08/">八月 2017<span class="sidebar_archives-count">3</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/07/">七月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2017/03/">三月 2017<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/09/">九月 2016<span class="sidebar_archives-count">2</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/07/">七月 2016<span class="sidebar_archives-count">6</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/06/">六月 2016<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2016/03/">三月 2016<span class="sidebar_archives-count">4</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/12/">十二月 2015<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/11/">十一月 2015<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/archives/2015/10/">十月 2015<span class="sidebar_archives-count">4</span></a>
            </ul>
        </li>
        
    

    <!-- Categories  -->
    
        <li class="dropdown">
            <a href="#" class="ripple-effect dropdown-toggle" data-toggle="dropdown">
                
                    <i class="material-icons sidebar-material-icons">chrome_reader_mode</i>
                
                分类
                <b class="caret"></b>
            </a>
            <ul class="dropdown-menu">
                <li>
                <a class="sidebar_archives-link" href="/categories/NET-Core/">.NET Core<span class="sidebar_archives-count">9</span></a></li><li><a class="sidebar_archives-link" href="/categories/DDD/">DDD<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Docker/">Docker<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Github-Pages/">Github Pages<span class="sidebar_archives-count">1</span></a></li><li><a class="sidebar_archives-link" href="/categories/Hexo/">Hexo<span class="sidebar_archives-count">2</span></a></li><li><a class="sidebar_archives-link" href="/categories/树莓派/">树莓派<span class="sidebar_archives-count">1</span></a>
            </ul>
        </li>
        
    

    <!-- Pages  -->
    
        <li>
            <a href="/tags.html" title="标签云">
                
                    <i class="material-icons sidebar-material-icons">local_offer</i>
                
                标签云
            </a>
        </li>
        
    
        <li>
            <a href="/timeline.html" title="时间轴">
                
                    <i class="material-icons sidebar-material-icons">list</i>
                
                时间轴
            </a>
        </li>
        
    
        <li>
            <a href="/contact.html" title="留言板">
                
                    <i class="material-icons sidebar-material-icons">forum</i>
                
                留言板
            </a>
        </li>
        
    
        <li>
            <a href="/about.html" title="关于我">
                
                    <i class="material-icons sidebar-material-icons">face</i>
                
                关于我
            </a>
        </li>
        
    
        <li>
            <a href="/gallery.html" title="相册">
                
                    <i class="material-icons sidebar-material-icons">image</i>
                
                相册
            </a>
        </li>
        
    
        <li>
            <a href="/friends.html" title="朋友">
                
                    <i class="material-icons sidebar-material-icons">people</i>
                
                朋友
            </a>
        </li>
        
    

    <!-- Article Number  -->
    
        <li>
            <a href="/archives">
                文章总数
                <span class="sidebar-badge">34</span>
            </a>
        </li>
        
    
</ul>


        <!-- Sidebar Footer -->
        <!--
I'm glad you use this theme, the development is no so easy, I hope you can keep the copyright, I will thank you so much.
If you still want to delete the copyrights, could you still retain the first one? Which namely "Theme Material"
It will not impact the appearance and can give developers a lot of support :)

很高兴您使用并喜欢该主题，开发不易 十分谢谢与希望您可以保留一下版权声明。
如果您仍然想删除的话 能否只保留第一项呢？即 "Theme Material"
它不会影响美观并可以给开发者很大的支持和动力。 :)
-->

<!-- Sidebar Divider -->


<!-- Theme Material -->


<!-- Help & Support -->
<!--

-->

<!-- Feedback -->
<!--

-->

<!-- About Theme -->
<!--

-->

    </div>

    <!-- Sidebar Image -->
    
    <span id="footer-image">
        <a href="https://ci.appveyor.com/project/ElderJames/elderjames-github-io" target="_blank" title="AppVeyor">
            <img src="https://ci.appveyor.com/api/projects/status/b0wack7uxrvifijj?svg=true" alt="AppVeyor"><!--
     --></a>
    </span>


</aside>

                    

                    
                        <!-- Footer Top Button -->
                        <div id="back-to-top" class="toTop-wrap">
    <a href="#top" class="toTop">
        <i class="material-icons footer_top-i">expand_less</i>
    </a>
</div>

                    

                    <!--Footer-->
<footer class="mdl-mini-footer" id="bottom" >
    
        <!-- Paradox Footer Left Section -->
        <div class="mdl-mini-footer--left-section sns-list">
    <!-- Twitter -->
    

    <!-- Facebook -->
    

    <!-- Google + -->
    

    <!-- Weibo -->
    

    <!-- Instagram -->
    

    <!-- Tumblr -->
    

    <!-- Github -->
    
        <a href="ElderJames" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-github">
                <span class="visuallyhidden">Github</span>
            </button><!--
     --></a>
    

    <!-- LinkedIn -->
    
        <a href="杨舜杰" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-linkedin">
                <span class="visuallyhidden">LinkedIn</span>
            </button><!--
     --></a>
    

    <!-- Zhihu -->
    
        <a href="ElderJames" target="_blank">
            <button class="mdl-mini-footer--social-btn social-btn footer-sns-zhihu">
                <span class="visuallyhidden">Zhihu</span>
            </button><!--
     --></a>
    

    <!-- Bilibili -->
    

    <!-- Telegram -->
    
    
    <!-- V2EX -->
    
</div>


        <!--Copyright-->
        <div id="copyright">
            Copyright&nbsp;©&nbsp;2015&nbsp;-<script type="text/javascript">var fd = new Date();document.write("&nbsp;" + fd.getFullYear() + "&nbsp;");</script>ElderJames' Blog
            
                <br>
                
                    学习/创作/分享<br /><a href="http://icp.chinaz.com/info?q=yangshunjie.com" rel="nofollow">粤ICP备15021181号-1</a>
                
            
        </div>

        <!-- Paradox Footer Right Section -->

        <!--
        I am glad you use this theme, the development is no so easy, I hope you can keep the copyright.
        It will not impact the appearance and can give developers a lot of support :)

        很高兴您使用该主题，开发不易，希望您可以保留一下版权声明。
        它不会影响美观并可以给开发者很大的支持。 :)
        -->

        <div class="mdl-mini-footer--right-section">
            <div>
                <div class="footer-develop-div">Powered by <a href="https://hexo.io" target="_blank" class="footer-develop-a">Hexo</a></div>
                <div class="footer-develop-div">Theme - <a href="https://github.com/viosey/hexo-theme-material" target="_blank" class="footer-develop-a">Material</a></div>
            </div>
        </div>
    
</footer>


                    <!-- Import JS File -->

    <script>lsloader.load("lazyload_js","/js/lazyload.min.js?1BcfzuNXqV+ntF6gq+5X3Q==", true)</script>



    <script>lsloader.load("js_js","/js/js.min.js?V/53wGualMuiPM3xoetD5Q==", true)</script>



    <script>lsloader.load("np_js","//cdn.bootcss.com/nprogress/0.2.0/nprogress.min.js", true)</script>


<script type="text/ls-javascript" id="NProgress-script">
    NProgress.configure({
        showSpinner: true
    });
    NProgress.start();
    $('#nprogress .bar').css({
        'background': '#29d'
    });
    $('#nprogress .peg').css({
        'box-shadow': '0 0 10px #29d, 0 0 15px #29d'
    });
    $('#nprogress .spinner-icon').css({
        'border-top-color': '#29d',
        'border-left-color': '#29d'
    });
    setTimeout(function() {
        NProgress.done();
        $('.fade').removeClass('out');
    }, 800);
</script>




    
        <script>lsloader.load("sm_js","/js/smoothscroll.js?lOy/ACj5suSNi7ZVFVbpFQ==", true)</script>
    





    <!-- Busuanzi -->
    <script src="https://dn-lbstatics.qbox.me/busuanzi/2.3/busuanzi.pure.mini.js"></script>



<!-- CommentBox -->
   <!-- GitTalk -->





<!-- UC Browser Compatible -->
<script>
	var agent = navigator.userAgent.toLowerCase();
	if(agent.indexOf('ucbrowser')>0) {
		document.write('<link rel="stylesheet" href="/css/uc.css">');
	   alert('由于 UC 浏览器使用极旧的内核，而本网站使用了一些新的特性。\n为了您能更好的浏览，推荐使用 Chrome 或 Firefox 浏览器。');
	}
</script>

<!-- Import prettify js  -->

    
        
            <script>lsloader.load("prettify_js","//cdn.bootcss.com/prettify/r298/prettify.min.js", true)</script>
        
    



<!-- Window Load -->
<!-- add class for prettify -->
<script type="text/ls-javascript" id="window-load">
    $(window).on('load', function() {
        // Post_Toc parent position fixed
        $('.post-toc-wrap').parent('.mdl-menu__container').css('position', 'fixed');
    });

    
        
            $(function() {
                $('pre').addClass('prettyprint linenums').attr('style', 'overflow:auto;');
                prettyPrint();
                })
        
    
    
</script>

<!-- MathJax Load-->


<!-- Bing Background -->

<script type="text/ls-javascript" id="Bing-Background-script">
    queue.offer(function(){
        $('body').attr('data-original', 'https://api.i-meto.com/bing?type=S');
    });
</script>


    <script type = "text/ls-javascript" id= "title-script">
        //网页当前状态判断
        var hidden, state, visibilityChange,timer;
        var leaveTitle='(●-●) 我奔溃啦~';
        var stayTitle='(*´∇｀*) 被发现啦~';
        var difaultTitle = document.title;
        var resetTime=1000
        
                leaveTitle += document.title;
                stayTitle += document.title;
        

        if (typeof document.hidden !== "undefined") {
            hidden = "hidden";
            visibilityChange = "visibilitychange";
            state = "visibilityState";
        } else if (typeof document.mozHidden !== "undefined") {
            hidden = "mozHidden";
            visibilityChange = "mozvisibilitychange";
            state = "mozVisibilityState";
        } else if (typeof document.msHidden !== "undefined") {
            hidden = "msHidden";
            visibilityChange = "msvisibilitychange";
            state = "msVisibilityState";
        } else if (typeof document.webkitHidden !== "undefined") {
            hidden = "webkitHidden";
            visibilityChange = "webkitvisibilitychange";
            state = "webkitVisibilityState";
        }
        // 添加监听器，在title里显示状态变化
        document.addEventListener(visibilityChange, function () {
           // document.title = document[state] =='visible'?stayTitle:leaveTitle ;
            if (timer!=null) clearTimeout(timer);
            if (document[state] =='visible'){
                document.title = stayTitle;
                timer=setTimeout(function(){
                    document.title=difaultTitle;
                },resetTime)
            }
            else{
                document.title = leaveTitle;
            }
        }, false);
        //初始化页面状态
        document.title = document[state] =='visible'?stayTitle:leaveTitle ;
    </script>
    

<script type="text/ls-javascript" id="lazy-load">
    // Offer LazyLoad
    queue.offer(function(){
        $('.lazy').lazyload({
            effect : 'show'
        });
    });

    // Start Queue
    $(document).ready(function(){
        setInterval(function(){
            queue.execNext();
        },200);
    });
</script>

<!-- Custom Footer -->



<script>
    (function(){
        var scriptList = document.querySelectorAll('script[type="text/ls-javascript"]')

        for (var i = 0; i < scriptList.length; ++i) {
            var item = scriptList[i];
            lsloader.runInlineScript(item.id,item.id);
        }
    })()
console.log('\n %c © Material Theme | Version: 1.5.0 | https://github.com/viosey/hexo-theme-material %c \n', 'color:#455a64;background:#e0e0e0;padding:5px 0;border-top-left-radius:5px;border-bottom-left-radius:5px;', 'color:#455a64;background:#e0e0e0;padding:5px 0;border-top-right-radius:5px;border-bottom-right-radius:5px;');
</script>

                </main>
            </div>
            
                
<div id="hexo-helper-live2d">
  <canvas id="live2dcanvas" width="150" height="300"></canvas>
</div>
<style>
  #live2dcanvas{
    position: fixed;
    width: 150px;
    height: 300px;
    opacity:0.7;
    right: 0px;
    z-index: 999;
    pointer-events: none;
    bottom: 60px;
  }
</style>
<script type="text/javascript" src="/live2d/device.min.js"></script>
<script type="text/javascript">
const loadScript = function loadScript(c,b){var a=document.createElement("script");a.type="text/javascript";"undefined"!=typeof b&&(a.readyState?a.onreadystatechange=function(){if("loaded"==a.readyState||"complete"==a.readyState)a.onreadystatechange=null,b()}:a.onload=function(){b()});a.src=c;document.body.appendChild(a)};
(function(){
  if((typeof(device) != 'undefined') && (device.mobile())){
    document.getElementById("live2dcanvas").style.width = '75px';
    document.getElementById("live2dcanvas").style.height = '150px';
  }else
    if (typeof(device) === 'undefined') console.error('Cannot find current-device script.');
  loadScript("/live2d/script.js", function(){loadlive2d("live2dcanvas", "/live2d/assets/tororo.model.json", 0.5);});
})();
</script>

            
        </body>
    
</html>
